<template>
  <div v-loading="loading" :class="[prefixCls, 'relative inline-block']" :style="wrapStyle">
    <component :is="tag" ref="wrapRef" @click="clickCode" />
    <div
      v-if="disabled"
      :class="`${prefixCls}--disabled`"
      class="absolute top-0 left-0 flex w-full h-full items-center justify-center"
      @click="disabledClick"
    >
      <div class="absolute top-[50%] left-[50%] font-bold">
        <Icon icon="vi-ep:refresh-right" :size="30" color="var(--el-color-primary)" />
        <div>{{ disabledText }}</div>
      </div>
    </div>
  </div>
</template>

<script>
import { nextTick, ref, watch, computed } from 'vue'
import QRCode from 'qrcode'
import { cloneDeep } from 'lodash-es'
import { isString } from '@/utils/is'
import { useDesign } from '@/hooks/web/useDesign'

export default {
  name: 'Qrcode',

  props: {
    tag: {
      type: String,
      default: 'canvas',
      validator: (val) => ['canvas', 'img'].includes(val)
    },
    text: {
      type: [String, Array],
      default: null
    },
    options: {
      type: Object,
      default: () => ({})
    },
    width: {
      type: Number,
      default: 200
    },
    logo: {
      type: [String, Object],
      default: ''
    },
    disabled: {
      type: Boolean,
      default: false
    },
    disabledText: {
      type: String,
      default: ''
    }
  },

  emits: ['done', 'click', 'disabled-click'],

  setup(props, { emit }) {
    const { getPrefixCls } = useDesign()
    const prefixCls = getPrefixCls('qrcode')

    const loading = ref(true)
    const wrapRef = ref(null)

    const renderText = computed(() => String(props.text))
    const wrapStyle = computed(() => ({
      width: props.width + 'px',
      height: props.width + 'px'
    }))

    const initQrcode = async () => {
      await nextTick()
      const options = cloneDeep(props.options || {})

      if (props.tag === 'canvas') {
        options.errorCorrectionLevel = options.errorCorrectionLevel || getErrorCorrectionLevel(renderText.value)
        const _width = await getOriginWidth(renderText.value, options)
        options.scale = props.width === 0 ? undefined : (props.width / _width) * 4

        const canvasRef = await QRCode.toCanvas(wrapRef.value, renderText.value, options)

        if (props.logo) {
          const url = await createLogoCode(canvasRef)
          emit('done', url)
        } else {
          emit('done', canvasRef.toDataURL())
        }

        loading.value = false
      } else {
        const url = await QRCode.toDataURL(renderText.value, {
          errorCorrectionLevel: 'H',
          width: props.width,
          ...options
        })
        wrapRef.value.src = url
        emit('done', url)
        loading.value = false
      }
    }

    const getOriginWidth = async (content, options) => {
      const _canvas = document.createElement('canvas')
      await QRCode.toCanvas(_canvas, content, options)
      return _canvas.width
    }

    const getErrorCorrectionLevel = (content) => {
      if (content.length > 36) return 'M'
      if (content.length > 16) return 'Q'
      return 'H'
    }

    const canvasRoundRect = (ctx) => (x, y, w, h, r) => {
      const minSize = Math.min(w, h)
      if (r > minSize / 2) r = minSize / 2
      ctx.beginPath()
      ctx.moveTo(x + r, y)
      ctx.arcTo(x + w, y, x + w, y + h, r)
      ctx.arcTo(x + w, y + h, x, y + h, r)
      ctx.arcTo(x, y + h, x, y, r)
      ctx.arcTo(x, y, x + w, y, r)
      ctx.closePath()
      return ctx
    }

    const createLogoCode = (canvasRef) => {
      const canvasWidth = canvasRef.width
      const logoOptions = Object.assign(
        {
          logoSize: 0.15,
          bgColor: '#ffffff',
          borderSize: 0.05,
          crossOrigin: 'anonymous',
          borderRadius: 8,
          logoRadius: 0
        },
        isString(props.logo) ? {} : props.logo
      )

      const {
        logoSize,
        bgColor,
        borderSize,
        crossOrigin,
        borderRadius,
        logoRadius
      } = logoOptions

      const logoSrc = isString(props.logo) ? props.logo : props.logo.src
      const logoWidth = canvasWidth * logoSize
      const logoXY = (canvasWidth * (1 - logoSize)) / 2
      const logoBgWidth = canvasWidth * (logoSize + borderSize)
      const logoBgXY = (canvasWidth * (1 - logoSize - borderSize)) / 2

      const ctx = canvasRef.getContext('2d')
      if (!ctx) return

      canvasRoundRect(ctx)(logoBgXY, logoBgXY, logoBgWidth, logoBgWidth, borderRadius)
      ctx.fillStyle = bgColor
      ctx.fill()

      const image = new Image()
      if (crossOrigin || logoRadius) {
        image.setAttribute('crossOrigin', crossOrigin)
      }
      image.src = logoSrc

      return new Promise((resolve) => {
        image.onload = () => {
          if (logoRadius) {
            const canvasImage = document.createElement('canvas')
            canvasImage.width = logoXY + logoWidth
            canvasImage.height = logoXY + logoWidth
            const imageCanvas = canvasImage.getContext('2d')
            if (!imageCanvas) return
            imageCanvas.drawImage(image, logoXY, logoXY, logoWidth, logoWidth)

            canvasRoundRect(ctx)(logoXY, logoXY, logoWidth, logoWidth, logoRadius)
            const fillStyle = ctx.createPattern(canvasImage, 'no-repeat')
            if (fillStyle) {
              ctx.fillStyle = fillStyle
              ctx.fill()
            }
          } else {
            ctx.drawImage(image, logoXY, logoXY, logoWidth, logoWidth)
          }
          resolve(canvasRef.toDataURL())
        }
      })
    }

    const clickCode = () => emit('click')
    const disabledClick = () => emit('disabled-click')

    watch(
      () => renderText.value,
      (val) => {
        if (!val) return
        initQrcode()
      },
      { immediate: true }
    )

    return {
      loading,
      prefixCls,
      wrapRef,
      wrapStyle,
      tag: props.tag,
      disabled: props.disabled,
      disabledText: props.disabledText,
      clickCode,
      disabledClick
    }
  }
}
</script>

<style scoped lang="less">
.qrcode--disabled {
  background: rgba(255, 255, 255, 0.95);
  & > div {
    transform: translate(-50%, -50%);
  }
}
</style>
